package client

import (
	"fmt"
	"github.com/sirupsen/logrus"
	"github.com/urfave/cli/v2"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"
)

func InitAction(context *cli.Context) (err error) {
	// 不然的话程序退出，主host必须重新挂载proc才行
	privateMountProc()

	updateHostname()
	updatePATH()

	setMountPoint()

	if err = execStartCmd(); err != nil {
		logrus.Errorf("err: %v", err)
	}

	return err
}

func privateMountProc() {
	// MS_PRIVATE 显式私有化配置，不然的话在该namespace里面mount proc的话，会反映在主host中，程序退出后，主host需要重新挂载proc
	// MS_REC ...
	if err := syscall.Mount("", "/", "", syscall.MS_PRIVATE|syscall.MS_REC, ""); err != nil {
		logrus.Errorf("again syscall.Mount err: %v", err)
	}

	// MS_NOEXEC 不允许在本文件系统运行其他程序
	// MS_NOSUID 在本系统中运行程序的时候，不允许设置set-user-ID或set-group-ID
	// MS_NODEV 这个参数是自 Linux 2.4 ，所 mount 统都会默 设定 参数
	flags := syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
	if err := syscall.Mount("proc", "/proc", "proc", uintptr(flags), ""); err != nil {
		logrus.Errorf("syscall.Mount err: %v", err)
	}
}

func updateHostname() {
	if err := exec.Command("hostname", "-b", "qqq").Run(); err != nil {
		logrus.Errorf("update hostname err: %v", err)
	}
}

func updatePATH() {
	err := os.Setenv("PATH", os.Getenv("PATH")+":/bin")
	if err != nil {
		logrus.Errorf("err: %v", err)
	}
}

func execStartCmd() (err error) {
	// 因为是额外的第4个文件描述符，所以下标为3
	pipe := os.NewFile(uintptr(3), "pipe")
	defer pipe.Close()

	var message []byte
	if message, err = ioutil.ReadAll(pipe); err == nil {
		command := strings.Split(string(message), " ")

		// 这个就是全路径了
		fullPath, _ := exec.LookPath(command[0])
		if fullPath == "" {
			logrus.Errorf("can not find start command: %v", command[0])
		}

		if err = syscall.Exec(fullPath, command[0:], os.Environ()); err != nil {
			logrus.Errorf("err: %v", err)
		}
	}
	return err
}

func setMountPoint() {
	mountPoint, _ := os.Getwd()
	if _, err := os.Stat(mountPoint); err != nil {
		os.MkdirAll(mountPoint, 0644)
	}
	pivotRoot(mountPoint)

	flags := syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
	syscall.Mount("proc", "/proc", "proc", uintptr(flags), "")
	syscall.Mount("tmpfs", "/dev", "tmpfs", syscall.MS_NOSUID|syscall.MS_STRICTATIME, "mode=755")
}

func pivotRoot(root string) error {
	// 为了使当前root的老 root 和新 root 不在同一个文件系统下，我们把root重新mount了一次, bind mount是把相同的内容换了一个挂载点的挂载方法
	if err := syscall.Mount(root, root, "bind", syscall.MS_BIND|syscall.MS_REC, ""); err != nil {
		return fmt.Errorf("mount rootfs to itself error: %v", err)
	}

	// 创建 rootfs/.pivot_root 存储 old_root
	pivotDir := filepath.Join(root, ".pivot_root")
	if err := os.Mkdir(pivotDir, 0777); err != nil {
		return err
	}

	// pivot_root 到新的rootfs, 现在老的 old_root 是挂载在rootfs/.pivot_root
	// 挂载点现在依然可以在mount命令中看到
	if err := syscall.PivotRoot(root, pivotDir); err != nil {
		return fmt.Errorf("pivot_root %v", err)
	}
	// 修改当前的工作目录到根目录
	if err := syscall.Chdir("/"); err != nil {
		return fmt.Errorf("chdir / %v", err)
	}

	pivotDir = filepath.Join("/", ".pivot_root")

	// umount rootfs/.pivot_root
	if err := syscall.Unmount(pivotDir, syscall.MNT_DETACH); err != nil {
		return fmt.Errorf("unmount pivot_root dir %v", err)
	}

	// 删除临时文件夹
	return os.Remove(pivotDir)
}
